//parser.cc用于对input中的html文件进行清洗，将处理的结果放入raw.txt
#include <iostream>
#include <vector>
#include <string>
#include <boost/filesystem.hpp>
#include "util.hpp"

//1.获取src_path文件中的.html文件到html_files中 
//2.清洗解析html_files中的文件放入到results 
//3.将results的文件写入dest_path，并且以\3作为分割符

const std::string src_path="./data/input";
const std::string dest_path="./data/raw_html/raw.txt";

//一个html页面的文档信息
typedef struct DocInfo{
    std::string title; //文档标题
    std::string content; //文档内容
    std::string url; //文档的url地址
}DocInfo_t;

//const &: 输入  表示只可以读不可以写，很安全
//*: 输出        表示可以读写，转入地址接收，直接修改
//&：输入输出    表示可以读写，避免拷贝

bool EnumFile(const std::string& src_path,std::vector<std::string>* html_Files);
bool ParserHtml(const std::vector<std::string>& html_Files,std::vector<DocInfo_t>* results);
bool SaveHtml(const std::vector<DocInfo_t>&results,const std::string& dest_path);

int main()
{
    //1.先实现第一步，将src_path文件中所有的.html文档存入html_files;
    std::vector<std::string> html_Files;
    while(!EnumFile(src_path,&html_Files))
    {
        std::cerr<<"Enum File Error!"<<std::endl;
        return 1;
    }

    //2.实现第二部，将html_files中的html数据进行清洗，再放入results中
    std::vector<DocInfo_t> results;
    while(!ParserHtml(html_Files,&results))
    {
        std::cerr<<"Parser Html Error!"<<std::endl;
        return 2;
    }

    //3.将results中的数据以\3进行分割，同时写入raw.txt
    // \3XXXXXXXXXX\3YYYYYY\3
    while(!SaveHtml(results,dest_path))
    {
        std::cerr<<"Save Html Error"<<std::endl;
        return 3;
    }

    return 0;
}

//EnumFile函数实现将src_path目录中的所有html文件，筛选递归存入html_files中
//我们将src_path中的路径信息递归保存在root_path中，注意路径文件是否合法
bool EnumFile(const std::string& src_path,std::vector<std::string>* html_Files)
{
    //path类对象 类型的root_path表示src_path目录中的根路径信息
    boost::filesystem::path root_path(src_path); //我们使用boost库中的path函数进行抽象的文件操作，注意命名空间
    
    //判断src_path路径是否存在
    if(!boost::filesystem::exists(root_path)) //exits()判断文件是否存在 
    {
        std::cerr<<"src_path :"<<src_path<<" File No Exists"<<std::endl;
        return false;
    }

    //使用迭代器进行递归，将所有的html文件存入html_Files
    boost::filesystem::recursive_directory_iterator end;
    for(boost::filesystem::recursive_directory_iterator iter(root_path);iter!=end;iter++)
    {
        //判断文件是不是普通文件文件，不是目录
        if(!boost::filesystem::is_regular_file(*iter)) //is_regular_file()判断普通文件
        {
            continue;
        }

        //判断是否为html文件
        if(iter->path().extension()!=".html") //extension()获取文件的后缀
        {
            continue;
        }

        //debug
        //std::cout<<iter->path().string()<<std::endl;

        //将当前的路径信息以字符串的形式存入html_Files中 
        html_Files->push_back(iter->path().string());
    }

    return true;
}

//提取文件的title
//<title>XXXXXYYYY</title>
//       |       |
//     begin    end 
static bool ParserHtml(const std::string& handle_file,std::string *title)
{
    //找到<title>标签
    std::size_t begin=handle_file.find("<title>");
    if(begin==std::string::npos)
    {
        return false;
    }

    //找到</title标签>中间的就是我们需要的title
    std::size_t end=handle_file.find("</title>");
    if(end==std::string::npos)
    {
        return false;
    }

    begin+=std::string("<title>").size();

    //如果<title>在</title>之后，直接报错
    if(begin>end)
    {
        return false;
    }

    //我们截取hand_file中的title放入doc.title中    
    *title=handle_file.substr(begin,end-begin);
    return true;
}

//提取文件中的内容
//<head>XXXXX</head><h>YYYY</h>
//XXXXXYYYY
static bool ParserContent(const std::string& handle_file,std::string *content)
{
    //我们基于一个简单的状态机来实现获取内容
    enum status{
        TABLE,
        CONTENT
    };

    //每一个字符的状态不是标签就是内容，开始的状态一定是标签
    enum status s=TABLE;
    for(char ch:handle_file)
    {
        switch(s) //检测当前的状态是什么
        {
            case TABLE: //当前的状态是TABLE <head>XXXXX</head>
                if(ch=='>') s=CONTENT; //如果是'>'说明后面是内容（注意<head></head>这种情况）
                break;
            case CONTENT: //当前的状态是CONTENT
                if(ch=='<') s=TABLE; //如果是'<'说明后面是标签
                else 
                {
                    if(ch=='\n') ch=' '; //避免'\n'和以后的文本分隔符冲突
                    content->push_back(ch);
                }
                break;
            default:
                break;
        }
    }
    return true;
}

//构建url
//官网URL样例： https://www.boost.org/doc/libs/1_84_0/doc/html/accumulators.html
//我们下载下来的url样例：boost_1_84_0/doc/html/accumulators.html
static bool ParserUrl(const std::string& file_path,std::string *url)
{
    //将我们本地的html路径和官网路径连接起来
    std::string url_head="https://www.boost.org/doc/libs/1_84_0/doc/html";
    std::string url_tail=file_path.substr(src_path.size());

    *url=url_head+url_tail;
    return true;
}

//进行debug
static void ShowDoc(DocInfo_t doc)
{
    std::cout<<"title: "<<doc.title<<std::endl;
    std::cout<<"content: "<<doc.content<<std::endl;
    std::cout<<"url: "<<doc.url<<std::endl;
}

//重要，ParserHtml函数用于解析html_files中的文件，并且存入results
//1.先读取html_files中的文件信息 2.对file的title content url进行解析且将存入doc 3.将doc存入results
bool ParserHtml(const std::vector<std::string>& html_Files,std::vector<DocInfo_t>* results)
{
    //1.先读取html_files中的单个文件信息file
    for(const std::string &file:html_Files)
    {
        //将file中的内容写入handle_file
        std::string handle_file;
        if(!my_util::FileUtil::ReadFile(file,&handle_file)) //ReadFile是我们实现的读取文件信息的工具
        {
            continue;
        }

        //2.对file文件中的内容(title content url)进行清洗，保存在doc
        DocInfo_t doc;

        //2.1使用ParserTitle提取文件中的title
        if(!ParserHtml(handle_file,&doc.title))
        {
            continue;
        }

        //2.2使用ParserContent去标签，提取文件中的content
        if(!ParserContent(handle_file,&doc.content))
        {
            continue;
        }

        //2.3使用ParserUrl构建url
        if(!ParserUrl(file,&doc.url))
        {
            continue;
        }

        //3.我们将已经完成解析的doc存入在results
        //results->push_back(doc);//bug:这里会产生拷贝，效率比较低
        results->push_back(std::move(doc));

        //debug 打印我们的doc信息
        //ShowDoc(doc);
        //break;
    }

    return true;
}

//将results的文件写入dest_path，并且以\3作为title,content,urld的分割符，\n作为文档和文档的分割符
//title\3content\3url\nXXXXX\3YYYYYYYY\3ZZZZ\n
bool SaveHtml(const std::vector<DocInfo_t>&results,const std::string& dest_path)
{
#define SEP '\3'
    //按照二进制的方式进行写入，打开文件
    std::ofstream out(dest_path,std::ios::out|std::ios::binary);
    if(!out.is_open())
    {
        std::cerr<<"open: "<<dest_path<<" failed"<<std::endl;
        return false;
    }

    //将不同的信息和文件之间加上分隔符
    for(auto& file:results)
    {
        std::string output;
        output+=file.title;
        output+=SEP;
        output+=file.content;
        output+=SEP;
        output+=file.url;
        output+='\n';

        out.write(output.c_str(),output.size());
    }

    //关闭文件
    out.close();

    return true;
}

